<?php
/*
# $Id: UrlCommand.php 11 2006-11-18 19:23:27Z James $
*/

/*
# @class UrlCommand
*/
class UrlCommand extends Object {

	/*
	# @property string $path
	# Stores the absolute folder-path that leads to the Controller class.
	*/
	private $path = '';

	/*
	# @property string|array $customUrlParser
	# Holds details of a custom url-parser function/method.
	*/
	static protected $customUrlParser = NULL;

	/*
	# @property string $controllerName
	# Controller name.
	*/
	private $controllerName = '';

	/*
	# @property string $actionName
	# Action name.
	*/
	private $actionName = '';

	/*
	# @property array $parameters
	# Action parameters.
	*/
	private $parameters = array();

	/*
	# @method UrlCommand __construct( [string $path, [string $controllerName, [string $actionName, [array $parameters]]]] )
	# $path				= Absolute folder path in which the Controller can be found
	# $controllerName	= Controller name in lower-hyphenated format, eg. controller-name
	# $actionName		= Action name in lower-hyphenated format, eg. action-name
	# $parameters		= Additional parameters to be passed to the Action (their order in this array is significant)
	#
	# Constructor.
	# A "new UrlCommand()" statement should only be issued internally within this class.
	# Use the "UrlCommand::create()" method instead within your application.
	*/
	function __construct($path='', $controllerName='index', $actionName='index', $parameters=array()) {

		// Store properties
		$this->path = $path;
		$this->controllerName = $controllerName;
		$this->actionName = $actionName;
		$this->parameters = $parameters;
	}

	/*
	# @method UrlCommand create( string $commandString )
	# $commandString	= Command in the format "/controller/action/param1/param2/..."
	#
	# Creates a UrlCommand instance and populates it with the necessary
	# attributes determined by the command string.
	*/
	static public function create($commandString) {

		// If registered, pass the URI through to the custom parser
		if(self::$customUrlParser!==NULL) {
			if(is_array(self::$customUrlParser)) {
				if(is_string(self::$customUrlParser[0])) {
					eval("$c = ".self::$customUrlParser[0]."::".self::$customUrlParser[1]."(\"$commandString\");");
					return $c;
				}
				else {
					return self::$customUrlParser[0]->{self::$customUrlParser[1]}($commandString);
				}
			}
			else {
				return self::$customUrlParser($commandString);
			}
		}

		// Split command into it's individual components and ensure we have
		// at least a controller-name and an action-name (use a default of
		// "index" for each).
		$commandString = preg_replace("/^\/+/", "", preg_replace("/\/+$/", "", $commandString));
		$args = $commandString=='' ? array() : explode('/', $commandString);
		while(sizeof($args)<2) {
			$args[] = 'index';
		}
		$controllerName = array_shift($args);
		$actionName = array_shift($args);
		$parameters = $args;

		// Unencode all parameters
		foreach($parameters as $k=>$p) {
			$parameters[$k] = urldecode($p);
		}

		// Build a list of search components.
		// The order of this list is significant in that the search is carried
		// out in the same order, ie:
		//	app > core > extensions
		//
		$searchComponents = array(
			'app'=>array(
				'path'=>Config::get('app.dir.controllers'),
				'controllerName'=>$controllerName,
				'actionName'=>$actionName,
				'parameters'=>$parameters
			),
			'core'=>array(
				'path'=>Config::get('core.dir.controllers'),
				'controllerName'=>$controllerName,
				'actionName'=>$actionName,
				'parameters'=>$parameters
			)
		);
		$ext = Config::get('ext');
		if(is_array($ext)) {
			foreach($ext as $extName=>$extConfig) {
				if(isset($extConfig['dir']['controllers'])) {
					$searchComponents["ext-$extName"] = array(
						'path'=>$extConfig['dir']['controllers'],
						'controllerName'=>$controllerName,
						'actionName'=>$actionName,
						'parameters'=>$parameters
					);
				}
			}
		}

		// Search for the Controller's class
		while(!isset($componentIndex)) {

			// Find class file
			foreach($searchComponents as $k=>$C) {

				// Set class name
				$className = $searchComponents[$k]['className'] = Inflector::controllerCommand_controllerClass($C['controllerName']);

				// Find class file
				if(is_file($C['path']."/$className.php")) {
					$componentIndex = $k;
					break 2;
				}
			}

			// Match $controllerName to a sub-folder within one of the search paths
			foreach($searchComponents as $k=>$C) {
				if(is_dir($C['path']."/".$C['controllerName'])) {
					$params = $C['parameters'];
					$searchComponents[$k] = array(
						'path'=>$C['path']."/".$C['controllerName'],
						'controllerName'=>$C['actionName'],
						'actionName'=>count($C['parameters'])>0 ? array_shift($params) : 'index',
						'parameters'=>$params
					);
					continue 2;
				}
			}

			// Set all controller names to "unknown"
			foreach($searchComponents as $k=>$C) {
				if($C['controllerName']=='unknown' && $C['path']!='') {
					$searchComponents[$k]['path'] = preg_replace("/\/[^\/]+$/", "", $C['path']);
				}
				else {
					$searchComponents[$k]['controllerName'] = 'unknown';
				}
			}
		}

		// Create new UrlCommand
		$C = $searchComponents[$componentIndex];
		$command = new UrlCommand($C['path'], $C['controllerName'], $C['actionName'], $C['parameters']);
		return $command;
	}

	/*
	# @method void registerUrlParser( string|array $function )
	# $function	= Name of a global function (string), or class name/instance and method name (array)
	#
	# Regsiter a custom function/method to handle the generation of UrlCommands
	# from given URIs.
	*/
	static public function registerUrlParser($function) {

	}

	/*
	# @method string toString()
	#
	# Returns this command as a string representation, in the format:
	#	/controller-name/action-name/param0/param1
	*/
	public function toString() {

		// Result
		return preg_replace("/^\/+/", "/",
			preg_replace("/\/+/", "",
				'/'.$this->path.'/'.$this->controllerName.'/'.$this->actionName.'/'.implode('/', $this->parameters)
			)
		);
	}

	/*
	# @method string createUrl( [string $controllerName, [string $actionName, [mixed $param1, mixed $param2, ... ]]] )
	# $controllerName	= Controller name (eg. controller-name)
	# $actionName		= Action name (eg. action-name)
	# $paramX			= Action parameters. Order is significant
	#
	# Generate the command part of the URL (ie. everything after the domain name)
	# based on the given arguments.
	# If any of the $param arguments use non-numeric indexes, then the key=>value
	# pairs in those parameters will be added to the query element of the
	# generated URL (ie. after the "?" in the URL)
	*/
	static public function createUrl($controllerName='index', $actionName='index') {

		// Build the URL
		$numArgs = func_num_args();
		$urlPrefix = Config::get('app.command.urlPrefix');
		$url = $urlCopy = Config::get('app.urlRoot').'/'.($urlPrefix=='' ? '' : "$urlPrefix/").$controllerName.'/'.urlencode($actionName);
		$query = array();
		for($i=2; $i<$numArgs; $i++) {
			$param = func_get_arg($i);
			if(is_array($param)) {
				foreach($param as $k=>$p) {

					// Detect numeric key
					if((int)$k===$k) {
						$url .= '/'.urlencode($p);
					}

					// String key, append to query portion of the url
					else {
						$query[] = $p===NULL ? $k : (is_array($p) ? http_build_query(array($k=>$p)) : "{$k}=".urlencode($p));
					}
				}
			}
			else {
				$url .= '/'.urlencode($param);
			}
		}
		if($actionName=='index' && $url==$urlCopy) {
			$url = preg_replace("/\/index$/i", "", $url);
		}
		$url = preg_replace("/\/+/", "/", preg_replace("/\/$/", "", $url)).(count($query)>0 ? '?'.implode("&", $query) : '');

		// Result
		return $url;
	}

	/*
	# @method string createAbsoluteUrl( [string $controllerName, [string $actionName, [mixed $param1, mixed $param2, ...]]] )
	# $controllerName	= Controller name (in underscored format, eg. controller_name)
	# $actionName		= Action name (in underscored format, eg. action_name)
	#
	# Generates the same URL as UrlCommand::createUrl(), but prefixes this with the application's domain name.
	# Note that the protocol (eg. http, https, ftp, etc) is NOT included in the returned URL.
	*/
	static public function createAbsoluteUrl($controllerName='index', $actionName='index') {

		// Gather additional arguments into any array
		$params = array();
		for($i=2; $i<func_num_args(); $i++) {
			$param = func_get_arg($i);
			if(is_array($param)) {
				foreach($param as $k=>$p) {
					$params[$k] = $p;
				}
			}
			else {
				$params[] = $param;
			}
		}

		// Result
		return Config::get('app.domain').UrlCommand::createUrl($controllerName, $actionName, $params);
	}

	/*
	# @method View execute()
	#
	# Executes the command and returns the resulting View object.
	*/
	public function execute() {

		// Create an instance of the Controller specified in this command
		$className = Inflector::controllerCommand_controllerClass($this->controllerName);
		include_once($this->path."/$className.php");
		$controller = new $className($this->parameters);

		// Invoke the required action, passing any given parameters, and store the resulting View object
		$view = $controller->invokeAction($this->actionName);

		// Result
		return $view;
	}

	/*
	# @method string getControllerName()
	#
	# Returns the name of this command's controller.
	*/
	function getControllerName() {

		// Result
		return $this->controllerName;
	}

	/*
	# @method string getActionName()
	#
	# Returns the name of this command's action.
	*/
	function getActionName() {

		// Result
		return $this->actionName;
	}

	/*
	# @method string getParameters()
	#
	# Returns this command's parameters.
	*/
	function getParameters() {

		// Result
		return $this->parameters;
	}
}
?>